-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce graphql-cache #411
Conversation
@@ -11,7 +11,7 @@ gem 'puma', '~> 3.12' | |||
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder | |||
# gem 'jbuilder', '~> 2.5' | |||
# Use Redis adapter to run Action Cable in production | |||
gem 'redis', '~> 4.0' | |||
gem 'hiredis' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a more performant version of the redis gem
@@ -68,13 +68,14 @@ gem 'prometheus_exporter' | |||
|
|||
gem 'activerecord-import' | |||
gem 'oj' | |||
gem 'newrelic_rpm' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are simply not using it and it adds overhead to every API call
@@ -15,6 +15,5 @@ class Schema < GraphQL::Schema | |||
mutation Types::Mutation | |||
lazy_resolve(Promise, :sync) | |||
use GraphQL::Batch | |||
use GraphQL::Execution::Interpreter | |||
use GraphQL::Analysis::AST | |||
use GraphQL::Cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately the new interpreter is incompatible with gql-cache (https://graphql-ruby.org/queries/interpreter.html) (stackshareio/graphql-cache#71)
app/graphql/types/rule.rb
Outdated
@@ -35,7 +35,7 @@ def references | |||
if context[:"rule_references_#{object.id}"].nil? | |||
::CollectionLoader.for(::Rule, :rule_references) | |||
.load(object).then do |references| | |||
references.map { |ref| [ref.href, ref.label] }.to_json | |||
references.map { |ref| { href: ref.href, label: ref.label} }.to_json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just makes sure we always return the same object. Notice how references_from_context
doesn't return this object exactly.
config.namespace = 'compliance' # Cache key prefix for keys generated by graphql-cache | ||
config.cache = Rails.cache # The cache object to use for caching | ||
config.logger = Rails.logger # Logger to receive cache-related log messages | ||
config.expiry = 15552000 # 6 months (in seconds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably discuss this and even think more seriously about introducing an LRU cache redis instance directly. Fields are cached alongside its updated_at, e.g: compliance:Rule:rules/30b9d4c7-4924-4996-bd89-7e29117fccf7-20200227230834209106:title
to force us to realize whether the key can be used or not. It's a sort of 'automatic invalidation' as any time a rule is updated, the old entries will be useless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the old entries with older updated_at
in the key are kept in redis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, that's why I'd want to go with an instance like https://redis.io/topics/lru-cache
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, that's why I'd want to go with an instance like https://redis.io/topics/lru-cache
module Cache | ||
module DeconstructorExtensions | ||
def perform | ||
return raw.value if method == 'lazy' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GraphQL::Cache::Deconstructor
figures out which sort of value each field is returning. Based on that, the current methods cannot handle lazy methods, or Promises. This is just a way to handle those.
|
||
class Key | ||
def to_s | ||
@to_s ||= [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This overrides the way the keys are set - I'll comment more on this in a few
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good so far! Thanks for exploring this option - it looks much cleaner than the heavy caching implementation. I have a couple questions inline.
@@ -4,6 +4,7 @@ | |||
# rule results, compliant,and references | |||
module RulesPreload | |||
include GraphQL::Schema::Interface | |||
field_class GraphQL::Cache::Field |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this required for some reason? This module is implemented from classes which inherit from BaseObject
, so I thought the field_class would be set already.
config.namespace = 'compliance' # Cache key prefix for keys generated by graphql-cache | ||
config.cache = Rails.cache # The cache object to use for caching | ||
config.logger = Rails.logger # Logger to receive cache-related log messages | ||
config.expiry = 15552000 # 6 months (in seconds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the old entries with older updated_at
in the key are kept in redis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! Thanks for figuring this all out! 🥇 It's definitely faster in the UI. I tested with the in memory cache (not with redis), but the speed should be comparable. I also tested by directly querying the graphql API; Despite caching, it seems much of the time is still spent doing something else (note views and AR only take ~3 milliseconds): the complete response with cache hits still takes ~6 seconds. Hopefully most of that time is dev-related stuff like determining if there are any code changes or something. 🤷 Also note that this graphql query is simple - caching should really speed up those complex queries like we have in the UI.
Started POST "/api/compliance/graphql" for 172.18.0.1 at 2020-04-21 21:57:08 +0000
Processing by GraphqlController#query as JSON
Parameters: {"query"=>"{\n allProfiles {\n policy {\n id\n }\n }\n}", "variables"=>{}, "operationName"=>nil, "graphql"=>{"query"=>"{\n allProfiles {\n policy {\n id\n
}\n }\n}", "variables"=>{}, "operationName"=>nil}}
Account Load (0.3ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."account_number" = $1 LIMIT $2 [["account_number", "1212729"], ["LIMIT", 1]]
↳ app/controllers/concerns/authentication.rb:53
Profile Load (0.6ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."account_id" = $1 [["account_id", "a1f12604-dc7c-4b29-8433-78eb4118a3d8"]]
↳ app/controllers/graphql_controller.rb:8
ProfileHost Load (0.4ms) SELECT "profile_hosts".* FROM "profile_hosts" WHERE "profile_hosts"."profile_id" = $1 [["profile_id", "aa141eed-3237-4d5e-8658-3342b9c9f382"]]
↳ app/controllers/graphql_controller.rb:8
Host Load (0.6ms) SELECT "hosts".* FROM "hosts" WHERE "hosts"."id" = $1 [["id", "fb04c74a-b6cd-4981-81a5-67cd0b791768"]]
↳ app/controllers/graphql_controller.rb:8
Cache miss: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:policy)
Cache miss: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:id)
Completed 200 OK in 6619ms (Views: 0.1ms | ActiveRecord: 27.3ms)
Started POST "/api/compliance/graphql" for 172.18.0.1 at 2020-04-21 21:47:38 +0000
Processing by GraphqlController#query as JSON
Parameters: {"query"=>"{\n allProfiles {\n policy {\n id\n }\n }\n}", "variables"=>{}, "operationName"=>nil, "graphql"=>{"query"=>"{\n allProfiles {\n policy {\n id\n }\n }\n}", "variables"=>{}, "operationName"=>nil}}
Account Load (0.6ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."account_number" = $1 LIMIT $2 [["account_number", "1212729"], ["LIMIT", 1]]
↳ app/controllers/concerns/authentication.rb:53
Profile Load (0.8ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."account_id" = $1 [["account_id", "a1f12604-dc7c-4b29-8433-78eb4118a3d8"]]
↳ app/controllers/graphql_controller.rb:8
ProfileHost Load (0.8ms) SELECT "profile_hosts".* FROM "profile_hosts" WHERE "profile_hosts"."profile_id" = $1 [["profile_id", "aa141eed-3237-4d5e-8658-3342b9c9f382"]]
↳ app/controllers/graphql_controller.rb:8
Host Load (0.8ms) SELECT "hosts".* FROM "hosts" WHERE "hosts"."id" = $1 [["id", "fb04c74a-b6cd-4981-81a5-67cd0b791768"]]
↳ app/controllers/graphql_controller.rb:8
Cache hit: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:policy)
Cache hit: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:id)
Completed 200 OK in 5977ms (Views: 0.3ms | ActiveRecord: 3.0ms)
* GQL-cache POC * Don't cache batched fields * Cache lazy types * Wait for promises before saving to cache * Re-enable promise fields and to_s * Use hiredis * Uncomment timeout in schema * Rubocop fixes * Fix href/label test
* Fix transient profile test failure (#407) Signed-off-by: Andrew Kofink <[email protected]> * Hardcode latest RHEL supported versions (#408) * Hardcode latest RHEL supported versions * Add cache for latest supported versions * Freeze constant * Return an AR relation * Properly search for match ref_id/version * Task to import supported SSG benchmarks (#412) * Task to import supported SSG benchmarks * Regex way of getting RHEL number Co-Authored-By: Andrew Kofink <[email protected]> * Fix rubocop Co-authored-by: Andrew Kofink <[email protected]> * Introduce graphql-cache (#411) * GQL-cache POC * Don't cache batched fields * Cache lazy types * Wait for promises before saving to cache * Re-enable promise fields and to_s * Use hiredis * Uncomment timeout in schema * Rubocop fixes * Fix href/label test * Add benchmarks API endpoint (#409) Signed-off-by: Andrew Kofink <[email protected]> * Fixes for rake tasks (#414) * Require HostInventoryNotFound * Execute instead of invoking import SSG task * Add specific config for cache redis (#415) Co-authored-by: Andrew Kofink <[email protected]> Co-authored-by: Andrew Kofink <[email protected]>
This reverts commit 3854f85.
This reverts commit 3854f85.
* Revert "Introduce graphql-cache (RedHatInsights#411)" This reverts commit 3854f85. * Unify references JSON response
* GQL-cache POC * Don't cache batched fields * Cache lazy types * Wait for promises before saving to cache * Re-enable promise fields and to_s * Use hiredis * Uncomment timeout in schema * Rubocop fixes * Fix href/label test
This PR introduces graphql-cache. I'll comment in each file what are the relevant changes.